Set-up

We will be using the developing tidymodels framework this week for integrating with the different machine-learning libraries in a consistent manner. You can install this package from the usual RStudio Tools menu. All of the other tools that are strictly necessary for clustering are available in base R. For full flexibility, however, the ggdendro, protoclust, and heatmaply packages are recommended. If you want to explore further possibilities, look at the cluster package.

As you work through this lab, you will occasionally get error messages asking you to install other packages, too. Install whatever R asks for from the Tools menu, and then try running the chunk again.

library(tidyverse)
library(tidymodels)
library(protoclust)
library(ggdendro)
library(heatmaply)
library(spotifyr)
library(compmus)
source('spotify.R')

Classification

In order to demonstrate some of the principles of classification, we will try to identify some of the features that Spotify uses to designate playlists as โ€˜workoutโ€™ playlists. For a full analysis, we would need to delve deeper, but letโ€™s start with a comparison of three playlists: Indie Pop, Indie Party, and Indie Workout. For speed, this example will work with only the first 20 songs from each playlist, but you should feel free to use more if your computer can handle it.

After you have this section of the notebook working, try using other combinations of Spotify workout playlists with similarly-named non-workout playlists.

pop <- 
    get_playlist_audio_features('spotify', '37i9dQZF1DWWEcRhUVtL8n') %>% 
    slice(1:20) %>% 
    add_audio_analysis
party <- 
    get_playlist_audio_features('spotify', '37i9dQZF1DWTujiC7wfofZ') %>% 
    slice(1:20) %>% 
    add_audio_analysis
workout <- 
    get_playlist_audio_features('spotify', '37i9dQZF1DXaRL7xbcDl7X') %>% 
    slice(1:20) %>% 
    add_audio_analysis

As you think about this lab session โ€“ and your portfolio โ€“ think about the four kinds of validity that Sturm and Wiggins discussed in our reading for last week. Do these projects have:

We bind the three playlists together using the trick from Week 7, transpose the chroma vectors to a common tonic using the compmus_c_transpose function, and then summarise the vectors like we did when generating chromagrams and cepstrograms. Again, Aitchisonโ€™s clr transformation can help with chroma.

indie <- 
    pop %>% mutate(playlist = "Indie Pop") %>% 
    bind_rows(
        party %>% mutate(playlist = "Indie Party"),
        workout %>% mutate(playlist = "Indie Workout")) %>% 
    mutate(playlist = factor(playlist)) %>% 
    mutate(
        segments = 
            map2(segments, key, compmus_c_transpose)) %>% 
    mutate(
        pitches = 
            map(segments, 
                compmus_summarise, pitches, 
                method = 'mean', norm = 'manhattan'),
        timbre =
            map(
                segments,
                compmus_summarise, timbre,
                method = 'mean')) %>% 
    mutate(pitches = map(pitches, compmus_normalise, 'clr')) %>% 
    mutate_at(vars(pitches, timbre), map, bind_rows) %>% 
    unnest(cols = c(pitches, timbre))

Pre-processing

In the tidyverse approach, we can preprocess data with a recipe specifying what we are predicting and what variables we think might be useful for that prediction. Then we use step functions to do any data cleaning (usually centering and scaling, but step_range is a viable alternative that squeezes everything to be between 0 and 1). Finally we prep and juice the data.

indie_class <- 
    recipe(playlist ~
               danceability +
               energy +
               loudness +
               speechiness +
               acousticness +
               instrumentalness +
               liveness +
               valence +
               tempo +
               duration +
               C + `C#|Db` + D + `D#|Eb` +
               E + `F` + `F#|Gb` + G +
               `G#|Ab` + A + `A#|Bb` + B +
               c01 + c02 + c03 + c04 + c05 + c06 +
               c07 + c08 + c09 + c10 + c11 + c12,
           data = indie) %>% 
    step_center(all_predictors()) %>%
    step_scale(all_predictors()) %>%
    # step_range(all_predictors()) %>% 
    prep(indie) %>% 
    juice

Cross-Validation

The vfold_cv function sets up cross-validation. We will use 5-fold cross-validation here in the interest of speed, but 10-fold cross-validation is more typical.

indie_cv <- indie_class %>% vfold_cv(5)

Classification Algorithms

Your DataCamp tutorials this week introduced four classical algorithms for classification: \(k\)-nearest neighbour, naive Bayes, logistic regression, and decision trees. Other than naive Bayes, all of them can be implemented more simply in tidymodels. In order to use cross-validation, however, we need to write some local helper functions to fit the classifier on the training sets, predict the labels for the test/validation sets, and bind the results to the original data.

\(k\)-Nearest Neighbour

A \(k\)-nearest neighbour classifier often works just fine with only one neighbour. It is very sensitive to the choice of features, however. Letโ€™s check the performance as a baseline and come back to it later.

indie_knn <- 
    nearest_neighbor(mode = 'classification', neighbors = 1) %>% 
    set_engine('kknn')
predict_knn <- function(split)
    fit(indie_knn, playlist ~ ., data = analysis(split)) %>% 
    predict(assessment(split), type = 'class') %>%
    bind_cols(assessment(split))

After a little awkwardness with cross-validation, we can use conf_mat to get a confusion matrix.

indie_cv %>% 
    mutate(pred = map(splits, predict_knn)) %>% unnest(pred) %>% 
    conf_mat(truth = playlist, estimate = .pred_class)
               Truth
Prediction      Indie Party Indie Pop Indie Workout
  Indie Party             8         2             5
  Indie Pop               5         7            10
  Indie Workout           7        11             5

These matrices autoplot in two forms.

indie_cv %>% 
    mutate(pred = map(splits, predict_knn)) %>% unnest(pred) %>% 
    conf_mat(truth = playlist, estimate = .pred_class) %>% 
    autoplot(type = 'mosaic')

indie_cv %>% 
    mutate(pred = map(splits, predict_knn)) %>% unnest(pred) %>% 
    conf_mat(truth = playlist, estimate = .pred_class) %>% 
    autoplot(type = 'heatmap')

We can also compute statistics like accuracy, Cohenโ€™s kappa, or the J-measure. (Cohenโ€™s kappa and the J-measure are two popular methods to account for how well a classifier would do just by random chance; they also range from 0 to 1.)

indie_cv %>% 
    mutate(pred = map(splits, predict_knn)) %>% unnest(pred) %>% 
    metric_set(accuracy, kap, j_index)(truth = playlist, estimate = .pred_class)

Logistic and Multinomial Regression

In the two-class case, we use logistic regression, but beware if you have more than two classes! R will just build a classifier for the first two without warning.

indie_logistic <- 
    logistic_reg(mode = 'classification') %>% 
    set_engine('glm')
predict_logistic <- function(split)
    fit(indie_logistic, playlist ~ ., data = analysis(split)) %>% 
    predict(assessment(split), type = 'class') %>%
    bind_cols(assessment(split))

With three or more classes, we need multinomial regression instead. You can adjust the penalty parameter if you are feeling adventurous.

indie_multinom <- 
    multinom_reg(mode = 'classification', penalty = 0.1) %>% 
    set_engine('glmnet')
predict_multinom <- function(split)
    fit(indie_multinom, playlist ~ ., data = analysis(split)) %>% 
    predict(assessment(split), type = 'class') %>%
    bind_cols(assessment(split))

It is not a strong classifier for this problem.

indie_cv %>% 
    mutate(pred = map(splits, predict_multinom)) %>% unnest(pred) %>% 
    metric_set(accuracy, kap, j_index)(truth = playlist, estimate = .pred_class)

We can look at the most important features in the model by using the coef method.

indie_class %>%
    fit(indie_multinom, playlist ~ ., data = .) %>%
    pluck('fit') %>%
    coef(s = 0)
$`Indie Party`
35 x 1 sparse Matrix of class "dgCMatrix"
                           1
(Intercept)      -1.78285212
danceability     -2.53638971
energy            .         
loudness          .         
speechiness      -0.60388521
acousticness     -1.13132203
instrumentalness  .         
liveness          0.88748099
valence           7.65925707
tempo             1.74806561
duration          0.90272650
C                -0.85239128
`C#|Db`           4.90362948
D                 0.53885089
`D#|Eb`           .         
E                 .         
F                 2.36474010
`F#|Gb`          -2.76928962
G                 .         
`G#|Ab`           .         
A                 0.05896787
`A#|Bb`           .         
B                -0.97831090
c01               .         
c02               .         
c03               .         
c04               .         
c05              -0.00371209
c06               .         
c07               .         
c08               .         
c09              -0.57114900
c10               4.69233868
c11              -0.44930256
c12               .         

$`Indie Pop`
35 x 1 sparse Matrix of class "dgCMatrix"
                           1
(Intercept)       3.11390092
danceability      3.45026211
energy            .         
loudness          1.24589231
speechiness       4.71954826
acousticness      .         
instrumentalness -0.42555636
liveness          .         
valence          -3.80650180
tempo             .         
duration         -3.79951231
C                 .         
`C#|Db`          -4.96323745
D                -0.42936912
`D#|Eb`           .         
E                 3.81249352
F                 .         
`F#|Gb`           .         
G                 .         
`G#|Ab`           .         
A                -2.86818513
`A#|Bb`           .         
B                 0.01899732
c01               .         
c02               .         
c03               .         
c04               .         
c05               9.42624955
c06               .         
c07              -0.57712744
c08               3.45576116
c09               .         
c10               .         
c11               .         
c12               .         

$`Indie Workout`
35 x 1 sparse Matrix of class "dgCMatrix"
                             1
(Intercept)       -1.331048804
danceability       .          
energy            -0.008703007
loudness          -0.462533428
speechiness        .          
acousticness       2.343274705
instrumentalness   1.169862384
liveness         -12.314715746
valence            .          
tempo             -1.892540056
duration           .          
C                  .          
`C#|Db`            .          
D                  .          
`D#|Eb`            2.706392941
E                  .          
F                 -0.697028182
`F#|Gb`            0.178237512
G                  .          
`G#|Ab`            .          
A                  .          
`A#|Bb`            .          
B                  .          
c01                .          
c02               -2.519830116
c03                .          
c04                5.116776480
c05                .          
c06               -0.022005255
c07                6.832908719
c08               -2.452948641
c09                .          
c10                .          
c11                0.704548663
c12                .          

Decision Trees

Decision trees are nicely intuitive, and perform somewhat better here.

indie_tree <- 
    decision_tree(mode = 'classification') %>%
    set_engine('C5.0')
predict_tree <- function(split)
    fit(indie_tree, playlist ~ ., data = analysis(split)) %>% 
    predict(assessment(split), type = 'class') %>%
    bind_cols(assessment(split))
indie_cv %>% 
    mutate(pred = map(splits, predict_tree)) %>% unnest(pred) %>% 
    metric_set(accuracy, kap, j_index)(truth = playlist, estimate = .pred_class)

We can look at the whole tree with the summary command. Be careful not to read too much into the actual numerical values, however: remember that the features were standardised before we started classification. Without cross-validation, the algorithm looks much better from the summary than it actually was in practice, but we can still see that timbre features are important and chroma features probably arenโ€™t.

indie_class %>% 
    fit(indie_tree, playlist ~ ., data = .) %>% 
    pluck('fit') %>%
    summary

Call:
C5.0.default(x = x, y = y, trials = 1, control = C50::C5.0Control(minCases = 2, sample = 0))


C5.0 [Release 2.07 GPL Edition]     Wed Mar 18 01:24:26 2020
-------------------------------

Class specified by attribute `outcome'

Read 60 cases (35 attributes) from undefined.data

Decision tree:

acousticness > 1.061204: Indie Pop (12/1)
acousticness <= 1.061204:
:...acousticness > 0.1623779: Indie Workout (7)
    acousticness <= 0.1623779:
    :...c10 <= -0.8386642:
        :...speechiness <= -0.3888929: Indie Workout (4)
        :   speechiness > -0.3888929: Indie Pop (2)
        c10 > -0.8386642:
        :...c09 > 1.307889: Indie Pop (3)
            c09 <= 1.307889:
            :...c03 <= -2.067993: Indie Workout (2)
                c03 > -2.067993:
                :...F > 0.3436538:
                    :...energy <= -0.1647938: Indie Pop (3)
                    :   energy > -0.1647938: Indie Party (5/1)
                    F <= 0.3436538:
                    :...`A#\|Bb` > 1.649611: Indie Workout (2)
                        `A#\|Bb` <= 1.649611:
                        :...c07 > 1.513791: Indie Workout (2)
                            c07 <= 1.513791:
                            :...valence > -0.7504143: Indie Party (12)
                                valence <= -0.7504143:
                                :...c08 <= 0.1367396: Indie Workout (2)
                                    c08 > 0.1367396: Indie Party (4)


Evaluation on training data (60 cases):

        Decision Tree   
      ----------------  
      Size      Errors  

        13    2( 3.3%)   <<


       (a)   (b)   (c)    <-classified as
      ----  ----  ----
        20                (a): class Indie Party
         1    19          (b): class Indie Pop
               1    19    (c): class Indie Workout


    Attribute usage:

    100.00% acousticness
     68.33% c10
     58.33% c09
     53.33% c03
     50.00% F
     36.67% `A#\|Bb`
     33.33% c07
     30.00% valence
     13.33% energy
     10.00% speechiness
     10.00% c08


Time: 0.0 secs

Random Forests

indie_forest <- 
    rand_forest(mode = 'classification') %>% 
    set_engine('randomForest')
predict_forest <- function(split)
    fit(indie_forest, playlist ~ ., data = analysis(split)) %>% 
    predict(assessment(split), type = 'class') %>%
    bind_cols(assessment(split))
indie_cv %>% 
    mutate(pred = map(splits, predict_forest)) %>% 
    unnest(pred) %>% 
    metric_set(accuracy, kap, j_index)(truth = playlist, estimate = .pred_class)

Random forests give us the best-quality ranking of feature importance, and we can plot it with randomForest::varImpPlot. Again, it is clear that timbre, specifically Component 1 (power) and Component 11, is important. Note that because random forests are indeed random, the accuracy and feature rankings will vary (slightly) every time you re-run the code.

indie_class %>% 
    fit(indie_forest, playlist ~ ., data = .) %>% 
    pluck('fit') %>% 
    randomForest::varImpPlot()

Feature Selection

Letโ€™s try \(k\)-NN again with just the top features. We see much better results.

predict_knn_reduced <- function(split)
    fit(
        indie_knn, 
        playlist ~ c01 + liveness + acousticness + c02 + energy, 
        data = analysis(split)) %>% 
    predict(assessment(split), type = 'class') %>%
    bind_cols(assessment(split))
indie_cv %>% 
    mutate(pred = map(splits, predict_knn_reduced)) %>% unnest(pred) %>% 
    metric_set(accuracy, kap, j_index)(truth = playlist, estimate = .pred_class)
indie_cv %>% 
    mutate(pred = map(splits, predict_knn_reduced)) %>% unnest(pred) %>% 
    conf_mat(truth = playlist, estimate = .pred_class) %>% 
    autoplot(type = 'mosaic')

Armed with this feature set, perhaps we can make a better plot. Itโ€™s clear that the workout list has fewer live tracks, and that the party playlist is somewhat louder and higher on Components 1 and 2 than the pop list.

indie %>%
    ggplot(aes(x = c01, y = c02, colour = playlist, size = liveness)) +
    geom_point(alpha = 0.8) +
    scale_color_brewer(type = 'qual', palette = 'Accent') +
    labs(
        x = 'Timbre Component 1', 
        y = 'Timbre Component 2', 
        size = 'Liveness', 
        colour = 'Playlist'
    )

Can you get better performance by using more or fewer of the top features from the random forest?

Clustering (Optional)

The Bibliothรจque nationale de France (BnF) makes a large portion of its music collection available on Spotify, including an eclectic collection of curated playlists. The defining musical characteristics of these playlists are sometimes unclear: for example, they have a Halloween playlist. Perhaps clustering can help us organise and describe what kinds of musical selections make it into the BnFโ€™s playlist.

We begin by loading the playlist and summarising the pitch and timbre features, just like last week. Note that, also like last week, we use compmus_c_transpose to transpose the chroma features so that โ€“ depending on the accuracy of Spotifyโ€™s key estimation โ€“ we can interpret them as if every piece were in C major or C minor. Although this example includes no delta features, try adding them yourself if you are feeling comfortable with R!

halloween <- 
    get_playlist_audio_features('bnfcollection', '1vsoLSK3ArkpaIHmUaF02C') %>% 
    add_audio_analysis %>% 
    mutate(
        segments = 
            map2(segments, key, compmus_c_transpose)) %>% 
    mutate(
        pitches = 
            map(segments, 
                compmus_summarise, pitches, 
                method = 'mean', norm = 'manhattan'),
        timbre =
            map(
                segments,
                compmus_summarise, timbre,
                method = 'mean')) %>% 
    mutate(pitches = map(pitches, compmus_normalise, 'clr')) %>% 
    mutate_at(vars(pitches, timbre), map, bind_rows) %>% 
    unnest(cols = c(pitches, timbre))

Pre-processing

Remember that in the tidyverse approach, we can preprocess data with a recipe. In this case, instead of a label that we want to predict, we start with a label that will make the cluster plots readable. For most projects, the track name will be the best choice (although feel free to experiment with others). The code below uses str_trunc to clip the track name to a maximum of 20 characters, again in order to improve readability. The other change from last week is column_to_rownames, which is necessary for the plot labels to appear correctly.

Last week we also discussed that although standardising variables with step_center to make the mean 0 and step_scale to make the standard deviation 1 is the most common approach, sometimes step_range is a better alternative, which squashes or stretches every features so that it ranges from 0 to 1. For most classification algorithms, the difference is small; for clustering, the differences can be more noticable. Itโ€™s wise to try both.

halloween_juice <- 
    recipe(track.name ~
               danceability +
               energy +
               loudness +
               speechiness +
               acousticness +
               instrumentalness +
               liveness +
               valence +
               tempo +
               duration +
               C + `C#|Db` + D + `D#|Eb` +
               E + `F` + `F#|Gb` + G +
               `G#|Ab` + A + `A#|Bb` + B +
               c01 + c02 + c03 + c04 + c05 + c06 +
               c07 + c08 + c09 + c10 + c11 + c12,
           data = halloween) %>% 
    step_center(all_predictors()) %>%
    step_scale(all_predictors()) %>%
    # step_range(all_predictors()) %>% 
    prep(halloween %>% mutate(track.name = str_trunc(track.name, 20))) %>% 
    juice %>% 
    column_to_rownames('track.name')

Computing distances

When using step_center and step_scale, then the Euclidean distance is usual. When using step_range, then the Manhattan distance is also a good choice: this combination is known as Gowerโ€™s distance and has a long history in clustering.

After you have this section of the notebook working with Euclidean distance, try modifying it to use Gowerโ€™s distance.

halloween_dist <- dist(halloween_juice, method = 'euclidean')

Hierarchical clustering

As you learned in your DataCamp exercises this week, there are three primary types of linkage: single, average, and complete. Usually average or complete give the best results. We can use the ggendrogram function to make a more standardised plot of the results.

hclust(halloween_dist, method = 'single') %>% dendro_data %>% ggdendrogram

A more recent โ€“ and often superior โ€“ linkage function is minimax linkage, available in the protoclust package. It is more akin to \(k\)-means: at each step, it chooses an ideal centroid for every cluster such that the maximum distance between centroids and all members of their respective clusters is as small as possible.

protoclust(halloween_dist) %>% dendro_data %>% ggdendrogram

Try all four of these linkages. Which one looks the best? Which one sounds the best (when you listen to the tracks on Spotify)? Can you guess which features are separating the clusters?

k-Means

Unlike hierarchical clustering, k-means clustering returns a different results every time. Nonetheless, it can be a useful reality check on the stability of the clusters from hierarchical clustering.

Try different numbers of clusters and see which results are the most stable.

kmeans(halloween_juice, 4)
K-means clustering with 4 clusters of sizes 5, 2, 7, 6

Cluster means:
  danceability      energy    loudness speechiness acousticness instrumentalness    liveness
1   -0.9168866 -0.09565648  0.04274727 -0.62837629   -0.2771348        0.4410513  0.56188821
2    0.5081080  0.06821405  0.19540272 -0.08024282   -0.3408504        0.4588850 -0.89728005
3    0.8866356  0.93909123  0.84609924  0.85021922   -0.3662239       -0.2242270 -0.01779592
4   -0.4397054 -1.03863072 -1.08787274 -0.44152791    0.7718237       -0.2589062 -0.14838492
     valence       tempo   duration            C      C#|Db          D       D#|Eb          E
1 -0.4536494 -0.36865124 -0.2179715  0.258484457 -0.6833355  0.4248468 -0.21559098  1.1969673
2  0.5451643  0.19468869 -0.5870669 -0.875041414  1.4179889  1.5859285 -1.44260505 -1.3583614
3  0.8768736  0.17842765 -0.2982245  0.062311297 -0.1287512 -0.3072340  0.04728937 -0.3249789
4 -0.8266995  0.03414755  0.7252605  0.003580244  0.2469930 -0.5242421  0.60535657 -0.1655436
            F      F#|Gb           G       G#|Ab          A      A#|Bb          B        c01
1 -0.18647403 -0.6425998  0.08079254 -0.94006032  0.4212267 -0.9058901  0.8885227  0.7812260
2 -0.67457284  2.2071822  1.30025415  0.05420713 -0.4323913 -0.8310957 -0.5304488 -0.3034831
3  0.06016135  0.1576683 -0.50878092  0.13519034  0.2728931  0.4500771  0.1026522  0.4571862
4  0.31006440 -0.3841739  0.09283258  0.60759250 -0.5252671  0.5068503 -0.6833802 -1.0832445
         c02         c03         c04        c05        c06         c07        c08        c09
1  0.9616765  0.99474968 -0.04951795  0.2721605 -0.5969236 -0.54702365 -0.5949745  0.1906642
2 -0.1844395 -1.32821483  0.15230482 -0.6988863 -0.1446833  0.29754667 -0.6677161 -0.3762457
3  0.3033856 -0.40324528  0.24925101 -0.1033827  0.5991044  0.35084069  0.3799168 -0.1968883
4 -1.0938671  0.08423303 -0.30029616  0.1267749 -0.1532911 -0.05264332  0.2751478  0.1962314
         c10        c11        c12
1 -0.2892562  0.8331551  0.5543391
2  0.6902069  0.1415686  0.3740885
3  0.4170187 -0.7718281 -0.3822938
4 -0.4755440  0.1589806 -0.1406360

Clustering vector:
I Put a Spell on You      Close Your Eyes           Evil Woman         Time to Kill 
                   3                    4                    3                    2 
      Evil Bad Woman The Princess of Evil      'Round Midnight Tana's Theme - Fr... 
                   4                    4                    4                    1 
          Evil Blues  Someone Is Watching Violin Sonata in ...         Cannibal Pot 
                   3                    1                    1                    3 
           Red Devil You're Not Living...         Little Demon Devil at 4 O'Cloc... 
                   2                    1                    3                    1 
      Old Devil Moon  Up Jumped the Devil Beetween the Devi...          Devil Woman 
                   3                    3                    4                    4 

Within cluster sum of squares by cluster:
[1] 126.46108  38.93083 137.62738 126.55590
 (between_SS / total_SS =  33.5 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss" "betweenss"   
[7] "size"         "iter"         "ifault"      

Heatmaps

Especially for storyboards, it can be helpful to visualise hierarchical clusterings along with heatmaps of feature values. We can do that with heatmaply. Although the interactive heatmaps are flashly, think carefully when deciding whether this representation is more helpful for your storyboard than the simpler dendrograms above.

grDevices::dev.size("px")
[1] 525.0000 324.4747
heatmaply(
    halloween_juice,
    hclustfun = hclust,
    # hclustfun = protoclust,
    # Comment out the hclust_method line when using protoclust.
    hclust_method = 'average',
    dist_method = 'euclidean')

Which features seem to be the most and least useful for the clustering? What happens if you re-run this section of the notebook using only the best features?

LS0tCnRpdGxlOiAiV2VlayAxMiDCtyBDbGFzc2lmaWNhdGlvbiBhbmQgQ2x1c3RlcmluZyIKYXV0aG9yOiAiSm9obiBBc2hsZXkgQnVyZ295bmUiCmRhdGU6ICIxOCBNYXJjaCAyMDIwIgpvdXRwdXQ6IAogICAgaHRtbF9ub3RlYm9vazoKICAgICAgICB0aGVtZTogZmxhdGx5Ci0tLQoKIyMgU2V0LXVwCgpXZSB3aWxsIGJlIHVzaW5nIHRoZSBkZXZlbG9waW5nIGB0aWR5bW9kZWxzYCBmcmFtZXdvcmsgdGhpcyB3ZWVrIGZvciBpbnRlZ3JhdGluZyB3aXRoIHRoZSBkaWZmZXJlbnQgbWFjaGluZS1sZWFybmluZyBsaWJyYXJpZXMgaW4gYSBjb25zaXN0ZW50IG1hbm5lci4gWW91IGNhbiBpbnN0YWxsIHRoaXMgcGFja2FnZSBmcm9tIHRoZSB1c3VhbCBSU3R1ZGlvIFRvb2xzIG1lbnUuIEFsbCBvZiB0aGUgb3RoZXIgdG9vbHMgdGhhdCBhcmUgc3RyaWN0bHkgbmVjZXNzYXJ5IGZvciBjbHVzdGVyaW5nIGFyZSBhdmFpbGFibGUgaW4gYmFzZSBSLiBGb3IgZnVsbCBmbGV4aWJpbGl0eSwgaG93ZXZlciwgdGhlIGBnZ2RlbmRyb2AsIGBwcm90b2NsdXN0YCwgYW5kIGBoZWF0bWFwbHlgIHBhY2thZ2VzIGFyZSByZWNvbW1lbmRlZC4gSWYgeW91IHdhbnQgdG8gZXhwbG9yZSBmdXJ0aGVyIHBvc3NpYmlsaXRpZXMsIGxvb2sgYXQgdGhlIGBjbHVzdGVyYCBwYWNrYWdlLgoKQXMgeW91IHdvcmsgdGhyb3VnaCB0aGlzIGxhYiwgeW91IHdpbGwgb2NjYXNpb25hbGx5IGdldCBlcnJvciBtZXNzYWdlcyBhc2tpbmcgeW91IHRvIGluc3RhbGwgb3RoZXIgcGFja2FnZXMsIHRvby4gSW5zdGFsbCB3aGF0ZXZlciBSIGFza3MgZm9yIGZyb20gdGhlIFRvb2xzIG1lbnUsIGFuZCB0aGVuIHRyeSBydW5uaW5nIHRoZSBjaHVuayBhZ2Fpbi4KCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5bW9kZWxzKQpsaWJyYXJ5KHByb3RvY2x1c3QpCmxpYnJhcnkoZ2dkZW5kcm8pCmxpYnJhcnkoaGVhdG1hcGx5KQpsaWJyYXJ5KHNwb3RpZnlyKQpsaWJyYXJ5KGNvbXBtdXMpCnNvdXJjZSgnc3BvdGlmeS5SJykKYGBgCgojIyBDbGFzc2lmaWNhdGlvbgoKSW4gb3JkZXIgdG8gZGVtb25zdHJhdGUgc29tZSBvZiB0aGUgcHJpbmNpcGxlcyBvZiBjbGFzc2lmaWNhdGlvbiwgd2Ugd2lsbCB0cnkgdG8gaWRlbnRpZnkgc29tZSBvZiB0aGUgZmVhdHVyZXMgdGhhdCBTcG90aWZ5IHVzZXMgdG8gZGVzaWduYXRlIHBsYXlsaXN0cyBhcyAnd29ya291dCcgcGxheWxpc3RzLiBGb3IgYSBmdWxsIGFuYWx5c2lzLCB3ZSB3b3VsZCBuZWVkIHRvIGRlbHZlIGRlZXBlciwgYnV0IGxldCdzIHN0YXJ0IHdpdGggYSBjb21wYXJpc29uIG9mIHRocmVlIHBsYXlsaXN0czogSW5kaWUgUG9wLCBJbmRpZSBQYXJ0eSwgYW5kIEluZGllIFdvcmtvdXQuIEZvciBzcGVlZCwgdGhpcyBleGFtcGxlIHdpbGwgd29yayB3aXRoIG9ubHkgdGhlIGZpcnN0IDIwIHNvbmdzIGZyb20gZWFjaCBwbGF5bGlzdCwgYnV0IHlvdSBzaG91bGQgZmVlbCBmcmVlIHRvIHVzZSBtb3JlIGlmIHlvdXIgY29tcHV0ZXIgY2FuIGhhbmRsZSBpdC4KCioqQWZ0ZXIgeW91IGhhdmUgdGhpcyBzZWN0aW9uIG9mIHRoZSBub3RlYm9vayB3b3JraW5nLCB0cnkgdXNpbmcgb3RoZXIgY29tYmluYXRpb25zIG9mIFNwb3RpZnkgd29ya291dCBwbGF5bGlzdHMgd2l0aCBzaW1pbGFybHktbmFtZWQgbm9uLXdvcmtvdXQgcGxheWxpc3RzLioqCgpgYGB7cn0KcG9wIDwtIAogICAgZ2V0X3BsYXlsaXN0X2F1ZGlvX2ZlYXR1cmVzKCdzcG90aWZ5JywgJzM3aTlkUVpGMURXV0VjUmhVVnRMOG4nKSAlPiUgCiAgICBzbGljZSgxOjIwKSAlPiUgCiAgICBhZGRfYXVkaW9fYW5hbHlzaXMKcGFydHkgPC0gCiAgICBnZXRfcGxheWxpc3RfYXVkaW9fZmVhdHVyZXMoJ3Nwb3RpZnknLCAnMzdpOWRRWkYxRFdUdWppQzd3Zm9mWicpICU+JSAKICAgIHNsaWNlKDE6MjApICU+JSAKICAgIGFkZF9hdWRpb19hbmFseXNpcwp3b3Jrb3V0IDwtIAogICAgZ2V0X3BsYXlsaXN0X2F1ZGlvX2ZlYXR1cmVzKCdzcG90aWZ5JywgJzM3aTlkUVpGMURYYVJMN3hiY0RsN1gnKSAlPiUgCiAgICBzbGljZSgxOjIwKSAlPiUgCiAgICBhZGRfYXVkaW9fYW5hbHlzaXMKYGBgCgpBcyB5b3UgdGhpbmsgYWJvdXQgdGhpcyBsYWIgc2Vzc2lvbiAtLSBhbmQgeW91ciBwb3J0Zm9saW8gLS0gdGhpbmsgYWJvdXQgdGhlIGZvdXIga2luZHMgb2YgdmFsaWRpdHkgdGhhdCBTdHVybSBhbmQgV2lnZ2lucyBkaXNjdXNzZWQgaW4gb3VyIHJlYWRpbmcgZm9yIGxhc3Qgd2Vlay4gRG8gdGhlc2UgcHJvamVjdHMgaGF2ZToKCiAgLSBTdGF0aXN0aWNhbCB2YWxpZGl0eSBbc29tZXdoYXQgYmV5b25kIHRoZSBzY29wZSBvZiB0aGlzIGNvdXJzZV0/CiAgLSBDb250ZW50IHZhbGlkaXR5PwogIC0gSW50ZXJuYWwgdmFsaWRpdHk/CiAgLSBFeHRlcm5hbCB2YWxpZGl0eT8KCldlIGJpbmQgdGhlIHRocmVlIHBsYXlsaXN0cyB0b2dldGhlciB1c2luZyB0aGUgdHJpY2sgZnJvbSBXZWVrIDcsIHRyYW5zcG9zZSB0aGUgY2hyb21hIHZlY3RvcnMgdG8gYSBjb21tb24gdG9uaWMgdXNpbmcgdGhlIGBjb21wbXVzX2NfdHJhbnNwb3NlYCBmdW5jdGlvbiwgYW5kIHRoZW4gc3VtbWFyaXNlIHRoZSB2ZWN0b3JzIGxpa2Ugd2UgZGlkIHdoZW4gZ2VuZXJhdGluZyBjaHJvbWFncmFtcyBhbmQgY2Vwc3Ryb2dyYW1zLiBBZ2FpbiwgQWl0Y2hpc29uJ3MgY2xyIHRyYW5zZm9ybWF0aW9uIGNhbiBoZWxwIHdpdGggY2hyb21hLgoKYGBge3J9CmluZGllIDwtIAogICAgcG9wICU+JSBtdXRhdGUocGxheWxpc3QgPSAiSW5kaWUgUG9wIikgJT4lIAogICAgYmluZF9yb3dzKAogICAgICAgIHBhcnR5ICU+JSBtdXRhdGUocGxheWxpc3QgPSAiSW5kaWUgUGFydHkiKSwKICAgICAgICB3b3Jrb3V0ICU+JSBtdXRhdGUocGxheWxpc3QgPSAiSW5kaWUgV29ya291dCIpKSAlPiUgCiAgICBtdXRhdGUocGxheWxpc3QgPSBmYWN0b3IocGxheWxpc3QpKSAlPiUgCiAgICBtdXRhdGUoCiAgICAgICAgc2VnbWVudHMgPSAKICAgICAgICAgICAgbWFwMihzZWdtZW50cywga2V5LCBjb21wbXVzX2NfdHJhbnNwb3NlKSkgJT4lIAogICAgbXV0YXRlKAogICAgICAgIHBpdGNoZXMgPSAKICAgICAgICAgICAgbWFwKHNlZ21lbnRzLCAKICAgICAgICAgICAgICAgIGNvbXBtdXNfc3VtbWFyaXNlLCBwaXRjaGVzLCAKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICdtZWFuJywgbm9ybSA9ICdtYW5oYXR0YW4nKSwKICAgICAgICB0aW1icmUgPQogICAgICAgICAgICBtYXAoCiAgICAgICAgICAgICAgICBzZWdtZW50cywKICAgICAgICAgICAgICAgIGNvbXBtdXNfc3VtbWFyaXNlLCB0aW1icmUsCiAgICAgICAgICAgICAgICBtZXRob2QgPSAnbWVhbicpKSAlPiUgCiAgICBtdXRhdGUocGl0Y2hlcyA9IG1hcChwaXRjaGVzLCBjb21wbXVzX25vcm1hbGlzZSwgJ2NscicpKSAlPiUgCiAgICBtdXRhdGVfYXQodmFycyhwaXRjaGVzLCB0aW1icmUpLCBtYXAsIGJpbmRfcm93cykgJT4lIAogICAgdW5uZXN0KGNvbHMgPSBjKHBpdGNoZXMsIHRpbWJyZSkpCmBgYAoKIyMjIFByZS1wcm9jZXNzaW5nCgpJbiB0aGUgYHRpZHl2ZXJzZWAgYXBwcm9hY2gsIHdlIGNhbiBwcmVwcm9jZXNzIGRhdGEgd2l0aCBhIGByZWNpcGVgIHNwZWNpZnlpbmcgd2hhdCB3ZSBhcmUgcHJlZGljdGluZyBhbmQgd2hhdCB2YXJpYWJsZXMgd2UgdGhpbmsgbWlnaHQgYmUgdXNlZnVsIGZvciB0aGF0IHByZWRpY3Rpb24uIFRoZW4gd2UgdXNlIGBzdGVwYCBmdW5jdGlvbnMgdG8gZG8gYW55IGRhdGEgY2xlYW5pbmcgKHVzdWFsbHkgY2VudGVyaW5nIGFuZCBzY2FsaW5nLCBidXQgYHN0ZXBfcmFuZ2VgIGlzIGEgdmlhYmxlIGFsdGVybmF0aXZlIHRoYXQgc3F1ZWV6ZXMgZXZlcnl0aGluZyB0byBiZSBiZXR3ZWVuIDAgYW5kIDEpLiBGaW5hbGx5IHdlIGBwcmVwYCBhbmQgYGp1aWNlYCB0aGUgZGF0YS4gCgpgYGB7cn0KaW5kaWVfY2xhc3MgPC0gCiAgICByZWNpcGUocGxheWxpc3QgfgogICAgICAgICAgICAgICBkYW5jZWFiaWxpdHkgKwogICAgICAgICAgICAgICBlbmVyZ3kgKwogICAgICAgICAgICAgICBsb3VkbmVzcyArCiAgICAgICAgICAgICAgIHNwZWVjaGluZXNzICsKICAgICAgICAgICAgICAgYWNvdXN0aWNuZXNzICsKICAgICAgICAgICAgICAgaW5zdHJ1bWVudGFsbmVzcyArCiAgICAgICAgICAgICAgIGxpdmVuZXNzICsKICAgICAgICAgICAgICAgdmFsZW5jZSArCiAgICAgICAgICAgICAgIHRlbXBvICsKICAgICAgICAgICAgICAgZHVyYXRpb24gKwogICAgICAgICAgICAgICBDICsgYEMjfERiYCArIEQgKyBgRCN8RWJgICsKICAgICAgICAgICAgICAgRSArIGBGYCArIGBGI3xHYmAgKyBHICsKICAgICAgICAgICAgICAgYEcjfEFiYCArIEEgKyBgQSN8QmJgICsgQiArCiAgICAgICAgICAgICAgIGMwMSArIGMwMiArIGMwMyArIGMwNCArIGMwNSArIGMwNiArCiAgICAgICAgICAgICAgIGMwNyArIGMwOCArIGMwOSArIGMxMCArIGMxMSArIGMxMiwKICAgICAgICAgICBkYXRhID0gaW5kaWUpICU+JSAKICAgIHN0ZXBfY2VudGVyKGFsbF9wcmVkaWN0b3JzKCkpICU+JQogICAgc3RlcF9zY2FsZShhbGxfcHJlZGljdG9ycygpKSAlPiUKICAgICMgc3RlcF9yYW5nZShhbGxfcHJlZGljdG9ycygpKSAlPiUgCiAgICBwcmVwKGluZGllKSAlPiUgCiAgICBqdWljZQpgYGAKCiMjIyBDcm9zcy1WYWxpZGF0aW9uCgpUaGUgYHZmb2xkX2N2YCBmdW5jdGlvbiBzZXRzIHVwIGNyb3NzLXZhbGlkYXRpb24uIFdlIHdpbGwgdXNlIDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uIGhlcmUgaW4gdGhlIGludGVyZXN0IG9mIHNwZWVkLCBidXQgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uIGlzIG1vcmUgdHlwaWNhbC4gCgpgYGB7cn0KaW5kaWVfY3YgPC0gaW5kaWVfY2xhc3MgJT4lIHZmb2xkX2N2KDUpCmBgYAoKIyMjIENsYXNzaWZpY2F0aW9uIEFsZ29yaXRobXMKCllvdXIgRGF0YUNhbXAgdHV0b3JpYWxzIHRoaXMgd2VlayBpbnRyb2R1Y2VkIGZvdXIgY2xhc3NpY2FsIGFsZ29yaXRobXMgZm9yIGNsYXNzaWZpY2F0aW9uOiAkayQtbmVhcmVzdCBuZWlnaGJvdXIsIG5haXZlIEJheWVzLCBsb2dpc3RpYyByZWdyZXNzaW9uLCBhbmQgZGVjaXNpb24gdHJlZXMuIE90aGVyIHRoYW4gbmFpdmUgQmF5ZXMsIGFsbCBvZiB0aGVtIGNhbiBiZSBpbXBsZW1lbnRlZCBtb3JlIHNpbXBseSBpbiBgdGlkeW1vZGVsc2AuIEluIG9yZGVyIHRvIHVzZSBjcm9zcy12YWxpZGF0aW9uLCBob3dldmVyLCB3ZSBuZWVkIHRvIHdyaXRlIHNvbWUgbG9jYWwgaGVscGVyIGZ1bmN0aW9ucyB0byBgZml0YCB0aGUgY2xhc3NpZmllciBvbiB0aGUgdHJhaW5pbmcgc2V0cywgYHByZWRpY3RgIHRoZSBsYWJlbHMgZm9yIHRoZSB0ZXN0L3ZhbGlkYXRpb24gc2V0cywgYW5kIGBiaW5kYCB0aGUgcmVzdWx0cyB0byB0aGUgb3JpZ2luYWwgZGF0YS4KCiMjIyMgJGskLU5lYXJlc3QgTmVpZ2hib3VyCgpBICRrJC1uZWFyZXN0IG5laWdoYm91ciBjbGFzc2lmaWVyIG9mdGVuIHdvcmtzIGp1c3QgZmluZSB3aXRoIG9ubHkgb25lIG5laWdoYm91ci4gSXQgaXMgdmVyeSBzZW5zaXRpdmUgdG8gdGhlIGNob2ljZSBvZiBmZWF0dXJlcywgaG93ZXZlci4gTGV0J3MgY2hlY2sgdGhlIHBlcmZvcm1hbmNlIGFzIGEgYmFzZWxpbmUgYW5kIGNvbWUgYmFjayB0byBpdCBsYXRlci4KCmBgYHtyfQppbmRpZV9rbm4gPC0gCiAgICBuZWFyZXN0X25laWdoYm9yKG1vZGUgPSAnY2xhc3NpZmljYXRpb24nLCBuZWlnaGJvcnMgPSAxKSAlPiUgCiAgICBzZXRfZW5naW5lKCdra25uJykKcHJlZGljdF9rbm4gPC0gZnVuY3Rpb24oc3BsaXQpCiAgICBmaXQoaW5kaWVfa25uLCBwbGF5bGlzdCB+IC4sIGRhdGEgPSBhbmFseXNpcyhzcGxpdCkpICU+JSAKICAgIHByZWRpY3QoYXNzZXNzbWVudChzcGxpdCksIHR5cGUgPSAnY2xhc3MnKSAlPiUKICAgIGJpbmRfY29scyhhc3Nlc3NtZW50KHNwbGl0KSkKYGBgCgpBZnRlciBhIGxpdHRsZSBhd2t3YXJkbmVzcyB3aXRoIGNyb3NzLXZhbGlkYXRpb24sIHdlIGNhbiB1c2UgYGNvbmZfbWF0YCB0byBnZXQgYSBjb25mdXNpb24gbWF0cml4LgoKYGBge3J9CmluZGllX2N2ICU+JSAKICAgIG11dGF0ZShwcmVkID0gbWFwKHNwbGl0cywgcHJlZGljdF9rbm4pKSAlPiUgdW5uZXN0KHByZWQpICU+JSAKICAgIGNvbmZfbWF0KHRydXRoID0gcGxheWxpc3QsIGVzdGltYXRlID0gLnByZWRfY2xhc3MpCmBgYAoKVGhlc2UgbWF0cmljZXMgYGF1dG9wbG90YCBpbiB0d28gZm9ybXMuCgpgYGB7cn0KaW5kaWVfY3YgJT4lIAogICAgbXV0YXRlKHByZWQgPSBtYXAoc3BsaXRzLCBwcmVkaWN0X2tubikpICU+JSB1bm5lc3QocHJlZCkgJT4lIAogICAgY29uZl9tYXQodHJ1dGggPSBwbGF5bGlzdCwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykgJT4lIAogICAgYXV0b3Bsb3QodHlwZSA9ICdtb3NhaWMnKQpgYGAKCmBgYHtyfQppbmRpZV9jdiAlPiUgCiAgICBtdXRhdGUocHJlZCA9IG1hcChzcGxpdHMsIHByZWRpY3Rfa25uKSkgJT4lIHVubmVzdChwcmVkKSAlPiUgCiAgICBjb25mX21hdCh0cnV0aCA9IHBsYXlsaXN0LCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKSAlPiUgCiAgICBhdXRvcGxvdCh0eXBlID0gJ2hlYXRtYXAnKQpgYGAKCldlIGNhbiBhbHNvIGNvbXB1dGUgc3RhdGlzdGljcyBsaWtlIGFjY3VyYWN5LCBDb2hlbidzIGthcHBhLCBvciB0aGUgSi1tZWFzdXJlLiAoQ29oZW4ncyBrYXBwYSBhbmQgdGhlIEotbWVhc3VyZSBhcmUgdHdvIHBvcHVsYXIgbWV0aG9kcyB0byBhY2NvdW50IGZvciBob3cgd2VsbCBhIGNsYXNzaWZpZXIgd291bGQgZG8ganVzdCBieSByYW5kb20gY2hhbmNlOyB0aGV5IGFsc28gcmFuZ2UgZnJvbSAwIHRvIDEuKQoKYGBge3J9CmluZGllX2N2ICU+JSAKICAgIG11dGF0ZShwcmVkID0gbWFwKHNwbGl0cywgcHJlZGljdF9rbm4pKSAlPiUgdW5uZXN0KHByZWQpICU+JSAKICAgIG1ldHJpY19zZXQoYWNjdXJhY3ksIGthcCwgal9pbmRleCkodHJ1dGggPSBwbGF5bGlzdCwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykKYGBgCgojIyMjIExvZ2lzdGljIGFuZCBNdWx0aW5vbWlhbCBSZWdyZXNzaW9uCgpJbiB0aGUgdHdvLWNsYXNzIGNhc2UsIHdlIHVzZSBsb2dpc3RpYyByZWdyZXNzaW9uLCBidXQgYmV3YXJlIGlmIHlvdSBoYXZlIG1vcmUgdGhhbiB0d28gY2xhc3NlcyEgUiB3aWxsIGp1c3QgYnVpbGQgYSBjbGFzc2lmaWVyIGZvciB0aGUgZmlyc3QgdHdvIHdpdGhvdXQgd2FybmluZy4KCmBgYHtyfQppbmRpZV9sb2dpc3RpYyA8LSAKICAgIGxvZ2lzdGljX3JlZyhtb2RlID0gJ2NsYXNzaWZpY2F0aW9uJykgJT4lIAogICAgc2V0X2VuZ2luZSgnZ2xtJykKcHJlZGljdF9sb2dpc3RpYyA8LSBmdW5jdGlvbihzcGxpdCkKICAgIGZpdChpbmRpZV9sb2dpc3RpYywgcGxheWxpc3QgfiAuLCBkYXRhID0gYW5hbHlzaXMoc3BsaXQpKSAlPiUgCiAgICBwcmVkaWN0KGFzc2Vzc21lbnQoc3BsaXQpLCB0eXBlID0gJ2NsYXNzJykgJT4lCiAgICBiaW5kX2NvbHMoYXNzZXNzbWVudChzcGxpdCkpCmBgYAoKV2l0aCB0aHJlZSBvciBtb3JlIGNsYXNzZXMsIHdlIG5lZWQgbXVsdGlub21pYWwgcmVncmVzc2lvbiBpbnN0ZWFkLiBZb3UgY2FuIGFkanVzdCB0aGUgcGVuYWx0eSBwYXJhbWV0ZXIgaWYgeW91IGFyZSBmZWVsaW5nIGFkdmVudHVyb3VzLgoKYGBge3J9CmluZGllX211bHRpbm9tIDwtIAogICAgbXVsdGlub21fcmVnKG1vZGUgPSAnY2xhc3NpZmljYXRpb24nLCBwZW5hbHR5ID0gMC4xKSAlPiUgCiAgICBzZXRfZW5naW5lKCdnbG1uZXQnKQpwcmVkaWN0X211bHRpbm9tIDwtIGZ1bmN0aW9uKHNwbGl0KQogICAgZml0KGluZGllX211bHRpbm9tLCBwbGF5bGlzdCB+IC4sIGRhdGEgPSBhbmFseXNpcyhzcGxpdCkpICU+JSAKICAgIHByZWRpY3QoYXNzZXNzbWVudChzcGxpdCksIHR5cGUgPSAnY2xhc3MnKSAlPiUKICAgIGJpbmRfY29scyhhc3Nlc3NtZW50KHNwbGl0KSkKYGBgCgpJdCBpcyBub3QgYSBzdHJvbmcgY2xhc3NpZmllciBmb3IgdGhpcyBwcm9ibGVtLgoKYGBge3J9CmluZGllX2N2ICU+JSAKICAgIG11dGF0ZShwcmVkID0gbWFwKHNwbGl0cywgcHJlZGljdF9tdWx0aW5vbSkpICU+JSB1bm5lc3QocHJlZCkgJT4lIAogICAgbWV0cmljX3NldChhY2N1cmFjeSwga2FwLCBqX2luZGV4KSh0cnV0aCA9IHBsYXlsaXN0LCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQpgYGAKCldlIGNhbiBsb29rIGF0IHRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlcyBpbiB0aGUgbW9kZWwgYnkgdXNpbmcgdGhlIGBjb2VmYCBtZXRob2QuCgpgYGB7cn0KaW5kaWVfY2xhc3MgJT4lCiAgICBmaXQoaW5kaWVfbXVsdGlub20sIHBsYXlsaXN0IH4gLiwgZGF0YSA9IC4pICU+JQogICAgcGx1Y2soJ2ZpdCcpICU+JQogICAgY29lZihzID0gMCkKYGBgCgojIyMjIERlY2lzaW9uIFRyZWVzCgpEZWNpc2lvbiB0cmVlcyBhcmUgbmljZWx5IGludHVpdGl2ZSwgYW5kIHBlcmZvcm0gc29tZXdoYXQgYmV0dGVyIGhlcmUuCgpgYGB7cn0KaW5kaWVfdHJlZSA8LSAKICAgIGRlY2lzaW9uX3RyZWUobW9kZSA9ICdjbGFzc2lmaWNhdGlvbicpICU+JQogICAgc2V0X2VuZ2luZSgnQzUuMCcpCnByZWRpY3RfdHJlZSA8LSBmdW5jdGlvbihzcGxpdCkKICAgIGZpdChpbmRpZV90cmVlLCBwbGF5bGlzdCB+IC4sIGRhdGEgPSBhbmFseXNpcyhzcGxpdCkpICU+JSAKICAgIHByZWRpY3QoYXNzZXNzbWVudChzcGxpdCksIHR5cGUgPSAnY2xhc3MnKSAlPiUKICAgIGJpbmRfY29scyhhc3Nlc3NtZW50KHNwbGl0KSkKYGBgCgpgYGB7cn0KaW5kaWVfY3YgJT4lIAogICAgbXV0YXRlKHByZWQgPSBtYXAoc3BsaXRzLCBwcmVkaWN0X3RyZWUpKSAlPiUgdW5uZXN0KHByZWQpICU+JSAKICAgIG1ldHJpY19zZXQoYWNjdXJhY3ksIGthcCwgal9pbmRleCkodHJ1dGggPSBwbGF5bGlzdCwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykKYGBgCgpXZSBjYW4gbG9vayBhdCB0aGUgd2hvbGUgdHJlZSB3aXRoIHRoZSBgc3VtbWFyeWAgY29tbWFuZC4gQmUgY2FyZWZ1bCBub3QgdG8gcmVhZCB0b28gbXVjaCBpbnRvIHRoZSBhY3R1YWwgbnVtZXJpY2FsIHZhbHVlcywgaG93ZXZlcjogcmVtZW1iZXIgdGhhdCB0aGUgZmVhdHVyZXMgd2VyZSBzdGFuZGFyZGlzZWQgYmVmb3JlIHdlIHN0YXJ0ZWQgY2xhc3NpZmljYXRpb24uIFdpdGhvdXQgY3Jvc3MtdmFsaWRhdGlvbiwgdGhlIGFsZ29yaXRobSBsb29rcyBtdWNoIGJldHRlciBmcm9tIHRoZSBzdW1tYXJ5IHRoYW4gaXQgYWN0dWFsbHkgd2FzIGluIHByYWN0aWNlLCBidXQgd2UgY2FuIHN0aWxsIHNlZSB0aGF0IHRpbWJyZSBmZWF0dXJlcyBhcmUgaW1wb3J0YW50IGFuZCBjaHJvbWEgZmVhdHVyZXMgcHJvYmFibHkgYXJlbid0LiAKCmBgYHtyfQppbmRpZV9jbGFzcyAlPiUgCiAgICBmaXQoaW5kaWVfdHJlZSwgcGxheWxpc3QgfiAuLCBkYXRhID0gLikgJT4lIAogICAgcGx1Y2soJ2ZpdCcpICU+JQogICAgc3VtbWFyeQpgYGAKCiMjIyMgUmFuZG9tIEZvcmVzdHMKCmBgYHtyfQppbmRpZV9mb3Jlc3QgPC0gCiAgICByYW5kX2ZvcmVzdChtb2RlID0gJ2NsYXNzaWZpY2F0aW9uJykgJT4lIAogICAgc2V0X2VuZ2luZSgncmFuZG9tRm9yZXN0JykKcHJlZGljdF9mb3Jlc3QgPC0gZnVuY3Rpb24oc3BsaXQpCiAgICBmaXQoaW5kaWVfZm9yZXN0LCBwbGF5bGlzdCB+IC4sIGRhdGEgPSBhbmFseXNpcyhzcGxpdCkpICU+JSAKICAgIHByZWRpY3QoYXNzZXNzbWVudChzcGxpdCksIHR5cGUgPSAnY2xhc3MnKSAlPiUKICAgIGJpbmRfY29scyhhc3Nlc3NtZW50KHNwbGl0KSkKYGBgCgpgYGB7cn0KaW5kaWVfY3YgJT4lIAogICAgbXV0YXRlKHByZWQgPSBtYXAoc3BsaXRzLCBwcmVkaWN0X2ZvcmVzdCkpICU+JSAKICAgIHVubmVzdChwcmVkKSAlPiUgCiAgICBtZXRyaWNfc2V0KGFjY3VyYWN5LCBrYXAsIGpfaW5kZXgpKHRydXRoID0gcGxheWxpc3QsIGVzdGltYXRlID0gLnByZWRfY2xhc3MpCmBgYAoKUmFuZG9tIGZvcmVzdHMgZ2l2ZSB1cyB0aGUgYmVzdC1xdWFsaXR5IHJhbmtpbmcgb2YgZmVhdHVyZSBpbXBvcnRhbmNlLCBhbmQgd2UgY2FuIHBsb3QgaXQgd2l0aCBgcmFuZG9tRm9yZXN0Ojp2YXJJbXBQbG90YC4gQWdhaW4sIGl0IGlzIGNsZWFyIHRoYXQgdGltYnJlLCBzcGVjaWZpY2FsbHkgQ29tcG9uZW50IDEgKHBvd2VyKSBhbmQgQ29tcG9uZW50IDExLCBpcyBpbXBvcnRhbnQuIE5vdGUgdGhhdCBiZWNhdXNlIHJhbmRvbSBmb3Jlc3RzIGFyZSBpbmRlZWQgcmFuZG9tLCB0aGUgYWNjdXJhY3kgYW5kIGZlYXR1cmUgcmFua2luZ3Mgd2lsbCB2YXJ5IChzbGlnaHRseSkgZXZlcnkgdGltZSB5b3UgcmUtcnVuIHRoZSBjb2RlLgoKYGBge3J9CmluZGllX2NsYXNzICU+JSAKICAgIGZpdChpbmRpZV9mb3Jlc3QsIHBsYXlsaXN0IH4gLiwgZGF0YSA9IC4pICU+JSAKICAgIHBsdWNrKCdmaXQnKSAlPiUgCiAgICByYW5kb21Gb3Jlc3Q6OnZhckltcFBsb3QoKQpgYGAKCiMjIyMgRmVhdHVyZSBTZWxlY3Rpb24KCkxldCdzIHRyeSAkayQtTk4gYWdhaW4gd2l0aCBqdXN0IHRoZSB0b3AgZmVhdHVyZXMuIFdlIHNlZSBtdWNoIGJldHRlciByZXN1bHRzLgoKYGBge3J9CnByZWRpY3Rfa25uX3JlZHVjZWQgPC0gZnVuY3Rpb24oc3BsaXQpCiAgICBmaXQoCiAgICAgICAgaW5kaWVfa25uLCAKICAgICAgICBwbGF5bGlzdCB+IGMwMSArIGxpdmVuZXNzICsgYWNvdXN0aWNuZXNzICsgYzAyICsgZW5lcmd5LCAKICAgICAgICBkYXRhID0gYW5hbHlzaXMoc3BsaXQpKSAlPiUgCiAgICBwcmVkaWN0KGFzc2Vzc21lbnQoc3BsaXQpLCB0eXBlID0gJ2NsYXNzJykgJT4lCiAgICBiaW5kX2NvbHMoYXNzZXNzbWVudChzcGxpdCkpCmluZGllX2N2ICU+JSAKICAgIG11dGF0ZShwcmVkID0gbWFwKHNwbGl0cywgcHJlZGljdF9rbm5fcmVkdWNlZCkpICU+JSB1bm5lc3QocHJlZCkgJT4lIAogICAgbWV0cmljX3NldChhY2N1cmFjeSwga2FwLCBqX2luZGV4KSh0cnV0aCA9IHBsYXlsaXN0LCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQpgYGAKCmBgYHtyfQppbmRpZV9jdiAlPiUgCiAgICBtdXRhdGUocHJlZCA9IG1hcChzcGxpdHMsIHByZWRpY3Rfa25uX3JlZHVjZWQpKSAlPiUgdW5uZXN0KHByZWQpICU+JSAKICAgIGNvbmZfbWF0KHRydXRoID0gcGxheWxpc3QsIGVzdGltYXRlID0gLnByZWRfY2xhc3MpICU+JSAKICAgIGF1dG9wbG90KHR5cGUgPSAnbW9zYWljJykKYGBgCgpBcm1lZCB3aXRoIHRoaXMgZmVhdHVyZSBzZXQsIHBlcmhhcHMgd2UgY2FuIG1ha2UgYSBiZXR0ZXIgcGxvdC4gSXQncyBjbGVhciB0aGF0IHRoZSB3b3Jrb3V0IGxpc3QgaGFzIGZld2VyIGxpdmUgdHJhY2tzLCBhbmQgdGhhdCB0aGUgcGFydHkgcGxheWxpc3QgaXMgc29tZXdoYXQgbG91ZGVyIGFuZCBoaWdoZXIgb24gQ29tcG9uZW50cyAxIGFuZCAyIHRoYW4gdGhlIHBvcCBsaXN0LgoKYGBge3J9CmluZGllICU+JQogICAgZ2dwbG90KGFlcyh4ID0gYzAxLCB5ID0gYzAyLCBjb2xvdXIgPSBwbGF5bGlzdCwgc2l6ZSA9IGxpdmVuZXNzKSkgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuOCkgKwogICAgc2NhbGVfY29sb3JfYnJld2VyKHR5cGUgPSAncXVhbCcsIHBhbGV0dGUgPSAnQWNjZW50JykgKwogICAgbGFicygKICAgICAgICB4ID0gJ1RpbWJyZSBDb21wb25lbnQgMScsIAogICAgICAgIHkgPSAnVGltYnJlIENvbXBvbmVudCAyJywgCiAgICAgICAgc2l6ZSA9ICdMaXZlbmVzcycsIAogICAgICAgIGNvbG91ciA9ICdQbGF5bGlzdCcKICAgICkKYGBgCgoqKkNhbiB5b3UgZ2V0IGJldHRlciBwZXJmb3JtYW5jZSBieSB1c2luZyBtb3JlIG9yIGZld2VyIG9mIHRoZSB0b3AgZmVhdHVyZXMgZnJvbSB0aGUgcmFuZG9tIGZvcmVzdD8qKgoKIyMgQ2x1c3RlcmluZyAoT3B0aW9uYWwpCgpUaGUgQmlibGlvdGjDqHF1ZSBuYXRpb25hbGUgZGUgRnJhbmNlIChCbkYpIG1ha2VzIGEgbGFyZ2UgcG9ydGlvbiBvZiBpdHMgW211c2ljIGNvbGxlY3Rpb25dKGh0dHBzOi8vZ2FsbGljYS5ibmYuZnIvaHRtbC91bmQvYm5mLWNvbGxlY3Rpb24tc29ub3JlKSBhdmFpbGFibGUgb24gU3BvdGlmeSwgaW5jbHVkaW5nIGFuIGVjbGVjdGljIGNvbGxlY3Rpb24gb2YgY3VyYXRlZCBwbGF5bGlzdHMuIFRoZSBkZWZpbmluZyBtdXNpY2FsIGNoYXJhY3RlcmlzdGljcyBvZiB0aGVzZSBwbGF5bGlzdHMgYXJlIHNvbWV0aW1lcyB1bmNsZWFyOiBmb3IgZXhhbXBsZSwgdGhleSBoYXZlIGEgSGFsbG93ZWVuIHBsYXlsaXN0LiBQZXJoYXBzIGNsdXN0ZXJpbmcgY2FuIGhlbHAgdXMgb3JnYW5pc2UgYW5kIGRlc2NyaWJlIHdoYXQga2luZHMgb2YgbXVzaWNhbCBzZWxlY3Rpb25zIG1ha2UgaXQgaW50byB0aGUgQm5GJ3MgcGxheWxpc3QuCgpXZSBiZWdpbiBieSBsb2FkaW5nIHRoZSBwbGF5bGlzdCBhbmQgc3VtbWFyaXNpbmcgdGhlIHBpdGNoIGFuZCB0aW1icmUgZmVhdHVyZXMsIGp1c3QgbGlrZSBsYXN0IHdlZWsuIE5vdGUgdGhhdCwgYWxzbyBsaWtlIGxhc3Qgd2Vlaywgd2UgdXNlIGBjb21wbXVzX2NfdHJhbnNwb3NlYCB0byB0cmFuc3Bvc2UgdGhlIGNocm9tYSBmZWF0dXJlcyBzbyB0aGF0IC0tIGRlcGVuZGluZyBvbiB0aGUgYWNjdXJhY3kgb2YgU3BvdGlmeSdzIGtleSBlc3RpbWF0aW9uIC0tIHdlIGNhbiBpbnRlcnByZXQgdGhlbSBhcyBpZiBldmVyeSBwaWVjZSB3ZXJlIGluIEMgbWFqb3Igb3IgQyBtaW5vci4gQWx0aG91Z2ggdGhpcyBleGFtcGxlIGluY2x1ZGVzIG5vIGRlbHRhIGZlYXR1cmVzLCB0cnkgYWRkaW5nIHRoZW0geW91cnNlbGYgaWYgeW91IGFyZSBmZWVsaW5nIGNvbWZvcnRhYmxlIHdpdGggUiEKCmBgYHtyfQpoYWxsb3dlZW4gPC0gCiAgICBnZXRfcGxheWxpc3RfYXVkaW9fZmVhdHVyZXMoJ2JuZmNvbGxlY3Rpb24nLCAnMXZzb0xTSzNBcmtwYUlIbVVhRjAyQycpICU+JSAKICAgIGFkZF9hdWRpb19hbmFseXNpcyAlPiUgCiAgICBtdXRhdGUoCiAgICAgICAgc2VnbWVudHMgPSAKICAgICAgICAgICAgbWFwMihzZWdtZW50cywga2V5LCBjb21wbXVzX2NfdHJhbnNwb3NlKSkgJT4lIAogICAgbXV0YXRlKAogICAgICAgIHBpdGNoZXMgPSAKICAgICAgICAgICAgbWFwKHNlZ21lbnRzLCAKICAgICAgICAgICAgICAgIGNvbXBtdXNfc3VtbWFyaXNlLCBwaXRjaGVzLCAKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICdtZWFuJywgbm9ybSA9ICdtYW5oYXR0YW4nKSwKICAgICAgICB0aW1icmUgPQogICAgICAgICAgICBtYXAoCiAgICAgICAgICAgICAgICBzZWdtZW50cywKICAgICAgICAgICAgICAgIGNvbXBtdXNfc3VtbWFyaXNlLCB0aW1icmUsCiAgICAgICAgICAgICAgICBtZXRob2QgPSAnbWVhbicpKSAlPiUgCiAgICBtdXRhdGUocGl0Y2hlcyA9IG1hcChwaXRjaGVzLCBjb21wbXVzX25vcm1hbGlzZSwgJ2NscicpKSAlPiUgCiAgICBtdXRhdGVfYXQodmFycyhwaXRjaGVzLCB0aW1icmUpLCBtYXAsIGJpbmRfcm93cykgJT4lIAogICAgdW5uZXN0KGNvbHMgPSBjKHBpdGNoZXMsIHRpbWJyZSkpCmBgYAoKIyMjIFByZS1wcm9jZXNzaW5nCgpSZW1lbWJlciB0aGF0IGluIHRoZSBgdGlkeXZlcnNlYCBhcHByb2FjaCwgd2UgY2FuIHByZXByb2Nlc3MgZGF0YSB3aXRoIGEgYHJlY2lwZWAuIEluIHRoaXMgY2FzZSwgaW5zdGVhZCBvZiBhIGxhYmVsIHRoYXQgd2Ugd2FudCB0byBwcmVkaWN0LCB3ZSBzdGFydCB3aXRoIGEgbGFiZWwgdGhhdCB3aWxsIG1ha2UgdGhlIGNsdXN0ZXIgcGxvdHMgcmVhZGFibGUuIEZvciBtb3N0IHByb2plY3RzLCB0aGUgdHJhY2sgbmFtZSB3aWxsIGJlIHRoZSBiZXN0IGNob2ljZSAoYWx0aG91Z2ggZmVlbCBmcmVlIHRvIGV4cGVyaW1lbnQgd2l0aCBvdGhlcnMpLiBUaGUgY29kZSBiZWxvdyB1c2VzIGBzdHJfdHJ1bmNgIHRvIGNsaXAgdGhlIHRyYWNrIG5hbWUgdG8gYSBtYXhpbXVtIG9mIDIwIGNoYXJhY3RlcnMsIGFnYWluIGluIG9yZGVyIHRvIGltcHJvdmUgcmVhZGFiaWxpdHkuIFRoZSBvdGhlciBjaGFuZ2UgZnJvbSBsYXN0IHdlZWsgaXMgYGNvbHVtbl90b19yb3duYW1lc2AsIHdoaWNoIGlzIG5lY2Vzc2FyeSBmb3IgdGhlIHBsb3QgbGFiZWxzIHRvIGFwcGVhciBjb3JyZWN0bHkuCgpMYXN0IHdlZWsgd2UgYWxzbyBkaXNjdXNzZWQgdGhhdCBhbHRob3VnaCBzdGFuZGFyZGlzaW5nIHZhcmlhYmxlcyB3aXRoIGBzdGVwX2NlbnRlcmAgdG8gbWFrZSB0aGUgbWVhbiAwIGFuZCBgc3RlcF9zY2FsZWAgdG8gbWFrZSB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIDEgaXMgdGhlIG1vc3QgY29tbW9uIGFwcHJvYWNoLCBzb21ldGltZXMgYHN0ZXBfcmFuZ2VgIGlzIGEgYmV0dGVyIGFsdGVybmF0aXZlLCB3aGljaCBzcXVhc2hlcyBvciBzdHJldGNoZXMgZXZlcnkgZmVhdHVyZXMgc28gdGhhdCBpdCByYW5nZXMgZnJvbSAwIHRvIDEuIEZvciBtb3N0IGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobXMsIHRoZSBkaWZmZXJlbmNlIGlzIHNtYWxsOyBmb3IgY2x1c3RlcmluZywgdGhlIGRpZmZlcmVuY2VzIGNhbiBiZSBtb3JlIG5vdGljYWJsZS4gSXQncyB3aXNlIHRvIHRyeSBib3RoLgoKYGBge3J9CmhhbGxvd2Vlbl9qdWljZSA8LSAKICAgIHJlY2lwZSh0cmFjay5uYW1lIH4KICAgICAgICAgICAgICAgZGFuY2VhYmlsaXR5ICsKICAgICAgICAgICAgICAgZW5lcmd5ICsKICAgICAgICAgICAgICAgbG91ZG5lc3MgKwogICAgICAgICAgICAgICBzcGVlY2hpbmVzcyArCiAgICAgICAgICAgICAgIGFjb3VzdGljbmVzcyArCiAgICAgICAgICAgICAgIGluc3RydW1lbnRhbG5lc3MgKwogICAgICAgICAgICAgICBsaXZlbmVzcyArCiAgICAgICAgICAgICAgIHZhbGVuY2UgKwogICAgICAgICAgICAgICB0ZW1wbyArCiAgICAgICAgICAgICAgIGR1cmF0aW9uICsKICAgICAgICAgICAgICAgQyArIGBDI3xEYmAgKyBEICsgYEQjfEViYCArCiAgICAgICAgICAgICAgIEUgKyBgRmAgKyBgRiN8R2JgICsgRyArCiAgICAgICAgICAgICAgIGBHI3xBYmAgKyBBICsgYEEjfEJiYCArIEIgKwogICAgICAgICAgICAgICBjMDEgKyBjMDIgKyBjMDMgKyBjMDQgKyBjMDUgKyBjMDYgKwogICAgICAgICAgICAgICBjMDcgKyBjMDggKyBjMDkgKyBjMTAgKyBjMTEgKyBjMTIsCiAgICAgICAgICAgZGF0YSA9IGhhbGxvd2VlbikgJT4lIAogICAgc3RlcF9jZW50ZXIoYWxsX3ByZWRpY3RvcnMoKSkgJT4lCiAgICBzdGVwX3NjYWxlKGFsbF9wcmVkaWN0b3JzKCkpICU+JQogICAgIyBzdGVwX3JhbmdlKGFsbF9wcmVkaWN0b3JzKCkpICU+JSAKICAgIHByZXAoaGFsbG93ZWVuICU+JSBtdXRhdGUodHJhY2submFtZSA9IHN0cl90cnVuYyh0cmFjay5uYW1lLCAyMCkpKSAlPiUgCiAgICBqdWljZSAlPiUgCiAgICBjb2x1bW5fdG9fcm93bmFtZXMoJ3RyYWNrLm5hbWUnKQpgYGAKCiMjIyBDb21wdXRpbmcgZGlzdGFuY2VzCgpXaGVuIHVzaW5nIGBzdGVwX2NlbnRlcmAgYW5kIGBzdGVwX3NjYWxlYCwgdGhlbiB0aGUgRXVjbGlkZWFuIGRpc3RhbmNlIGlzIHVzdWFsLiBXaGVuIHVzaW5nIGBzdGVwX3JhbmdlYCwgdGhlbiB0aGUgTWFuaGF0dGFuIGRpc3RhbmNlIGlzIGFsc28gYSBnb29kIGNob2ljZTogdGhpcyBjb21iaW5hdGlvbiBpcyBrbm93biBhcyAqR293ZXIncyBkaXN0YW5jZSogYW5kIGhhcyBhIGxvbmcgaGlzdG9yeSBpbiBjbHVzdGVyaW5nLgoKKipBZnRlciB5b3UgaGF2ZSB0aGlzIHNlY3Rpb24gb2YgdGhlIG5vdGVib29rIHdvcmtpbmcgd2l0aCBFdWNsaWRlYW4gZGlzdGFuY2UsIHRyeSBtb2RpZnlpbmcgaXQgdG8gdXNlIEdvd2VyJ3MgZGlzdGFuY2UuKioKCmBgYHtyfQpoYWxsb3dlZW5fZGlzdCA8LSBkaXN0KGhhbGxvd2Vlbl9qdWljZSwgbWV0aG9kID0gJ2V1Y2xpZGVhbicpCmBgYAoKIyMjIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nCgpBcyB5b3UgbGVhcm5lZCBpbiB5b3VyIERhdGFDYW1wIGV4ZXJjaXNlcyB0aGlzIHdlZWssIHRoZXJlIGFyZSB0aHJlZSBwcmltYXJ5IHR5cGVzIG9mICpsaW5rYWdlKjogc2luZ2xlLCBhdmVyYWdlLCBhbmQgY29tcGxldGUuIFVzdWFsbHkgYXZlcmFnZSBvciBjb21wbGV0ZSBnaXZlIHRoZSBiZXN0IHJlc3VsdHMuIFdlIGNhbiB1c2UgdGhlIGBnZ2VuZHJvZ3JhbWAgZnVuY3Rpb24gdG8gbWFrZSBhIG1vcmUgc3RhbmRhcmRpc2VkIHBsb3Qgb2YgdGhlIHJlc3VsdHMuCgpgYGB7cn0KaGNsdXN0KGhhbGxvd2Vlbl9kaXN0LCBtZXRob2QgPSAnc2luZ2xlJykgJT4lIGRlbmRyb19kYXRhICU+JSBnZ2RlbmRyb2dyYW0KYGBgCgpBIG1vcmUgcmVjZW50IC0tIGFuZCBvZnRlbiBzdXBlcmlvciAtLSBsaW5rYWdlIGZ1bmN0aW9uIGlzICptaW5pbWF4IGxpbmthZ2UqLCBhdmFpbGFibGUgaW4gdGhlIGBwcm90b2NsdXN0YCBwYWNrYWdlLiBJdCBpcyBtb3JlIGFraW4gdG8gJGskLW1lYW5zOiBhdCBlYWNoIHN0ZXAsIGl0IGNob29zZXMgYW4gaWRlYWwgY2VudHJvaWQgZm9yIGV2ZXJ5IGNsdXN0ZXIgc3VjaCB0aGF0IHRoZSBtYXhpbXVtIGRpc3RhbmNlIGJldHdlZW4gY2VudHJvaWRzIGFuZCBhbGwgbWVtYmVycyBvZiB0aGVpciByZXNwZWN0aXZlIGNsdXN0ZXJzIGlzIGFzIHNtYWxsIGFzIHBvc3NpYmxlLgoKYGBge3J9CnByb3RvY2x1c3QoaGFsbG93ZWVuX2Rpc3QpICU+JSBkZW5kcm9fZGF0YSAlPiUgZ2dkZW5kcm9ncmFtCmBgYAoKKipUcnkgYWxsIGZvdXIgb2YgdGhlc2UgbGlua2FnZXMuIFdoaWNoIG9uZSBsb29rcyB0aGUgYmVzdD8gV2hpY2ggb25lICpzb3VuZHMqIHRoZSBiZXN0ICh3aGVuIHlvdSBsaXN0ZW4gdG8gdGhlIHRyYWNrcyBvbiBTcG90aWZ5KT8gQ2FuIHlvdSBndWVzcyB3aGljaCBmZWF0dXJlcyBhcmUgc2VwYXJhdGluZyB0aGUgY2x1c3RlcnM/KiogCgojIyMgKmsqLU1lYW5zCgpVbmxpa2UgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcsICprKi1tZWFucyBjbHVzdGVyaW5nIHJldHVybnMgYSBkaWZmZXJlbnQgcmVzdWx0cyBldmVyeSB0aW1lLiBOb25ldGhlbGVzcywgaXQgY2FuIGJlIGEgdXNlZnVsIHJlYWxpdHkgY2hlY2sgb24gdGhlIHN0YWJpbGl0eSBvZiB0aGUgY2x1c3RlcnMgZnJvbSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZy4KCioqVHJ5IGRpZmZlcmVudCBudW1iZXJzIG9mIGNsdXN0ZXJzIGFuZCBzZWUgd2hpY2ggcmVzdWx0cyBhcmUgdGhlIG1vc3Qgc3RhYmxlLioqCgpgYGB7cn0Ka21lYW5zKGhhbGxvd2Vlbl9qdWljZSwgNCkKYGBgCgojIyMgSGVhdG1hcHMKCkVzcGVjaWFsbHkgZm9yIHN0b3J5Ym9hcmRzLCBpdCBjYW4gYmUgaGVscGZ1bCB0byB2aXN1YWxpc2UgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmdzIGFsb25nIHdpdGggaGVhdG1hcHMgb2YgZmVhdHVyZSB2YWx1ZXMuIFdlIGNhbiBkbyB0aGF0IHdpdGggYGhlYXRtYXBseWAuIEFsdGhvdWdoIHRoZSBpbnRlcmFjdGl2ZSBoZWF0bWFwcyBhcmUgZmxhc2hseSwgdGhpbmsgY2FyZWZ1bGx5IHdoZW4gZGVjaWRpbmcgd2hldGhlciB0aGlzIHJlcHJlc2VudGF0aW9uIGlzIG1vcmUgaGVscGZ1bCBmb3IgeW91ciBzdG9yeWJvYXJkIHRoYW4gdGhlIHNpbXBsZXIgZGVuZHJvZ3JhbXMgYWJvdmUuIAoKYGBge3J9CmdyRGV2aWNlczo6ZGV2LnNpemUoInB4IikKaGVhdG1hcGx5KAogICAgaGFsbG93ZWVuX2p1aWNlLAogICAgaGNsdXN0ZnVuID0gaGNsdXN0LAogICAgIyBoY2x1c3RmdW4gPSBwcm90b2NsdXN0LAogICAgIyBDb21tZW50IG91dCB0aGUgaGNsdXN0X21ldGhvZCBsaW5lIHdoZW4gdXNpbmcgcHJvdG9jbHVzdC4KICAgIGhjbHVzdF9tZXRob2QgPSAnYXZlcmFnZScsCiAgICBkaXN0X21ldGhvZCA9ICdldWNsaWRlYW4nKQpgYGAKCioqV2hpY2ggZmVhdHVyZXMgc2VlbSB0byBiZSB0aGUgbW9zdCBhbmQgbGVhc3QgdXNlZnVsIGZvciB0aGUgY2x1c3RlcmluZz8gV2hhdCBoYXBwZW5zIGlmIHlvdSByZS1ydW4gdGhpcyBzZWN0aW9uIG9mIHRoZSBub3RlYm9vayB1c2luZyBvbmx5IHRoZSBiZXN0IGZlYXR1cmVzPyoqCg==